
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Graphics.hpp>
#include <cmath>
#include <map>
struct Agent;

#define WIDTH 256
#define HEIGHT 256

#define FREAZEPOINT 70
#define SEED 667

int randomAccessX[WIDTH];
int randomAccessY[HEIGHT];

void setupRandomAccessVector()
{
	for(unsigned int x = 0; x < WIDTH; x++)
	{
		randomAccessX[x] = x;
	}
	for(unsigned int y = 0; y < HEIGHT; y++)
	{
		randomAccessY[y] = y;
	}

	for(unsigned int x = 0; x < WIDTH; x++)
	{
		int index = rand()%WIDTH;
		int AccessPointValue = randomAccessX[index];
		int currentValue = randomAccessX[x];
		randomAccessX[x] = AccessPointValue;

		randomAccessX[index] = currentValue;
	}
	for(unsigned int y = 0; y < HEIGHT; y++)
	{
		int index = rand()%HEIGHT;
		int AccessPointValue = randomAccessY[index];
		int currentValue = randomAccessY[y];
		randomAccessY[y] = AccessPointValue;
		randomAccessY[index] = currentValue;
	}
}

class AgentVector
{
public:
	AgentVector()
	{
		clear();

	}
	void push_back(Agent* in)
	{
		dataelements++;
		Vector[latestIndex] = in; 
		gotData[latestIndex] = true;
		latestIndex++;
		size++;
	}

	Agent* popRandom()
	{
		Agent* currentAgent = NULL;

		if(dataelements)
		{
			int index = rand()%size;

			while(dataelements)
			{
				Agent* currentAgent = Vector[index];
				if(currentAgent)
				{
					Vector[index] = NULL;
					gotData[index] = false;
					dataelements--;
					return currentAgent;
				}
				else
				{
					index++;
					if (index > size-1)
					{
						index = 0;
					}
				}
			}
		}
		return currentAgent;
	}

	int size;
	int dataelements;
	int latestIndex;

	Agent* Vector[WIDTH*HEIGHT];
	bool gotData[WIDTH*HEIGHT];
	void clear()
	{
		dataelements =0;
		size = 0;
		latestIndex = 0;
		for(int i = 0; i < 64; i++)
		{
			Vector[i] = NULL;
			gotData[i] = false;
		}
	}
};


enum RenderStatus
{
	NORMAL,
	HEAT,
	MATERIAL,
	MOIST,
	CHANGESFROMLASTFRAME,
	NROFRENDERSTATUS

};

struct Material
{
	unsigned int Bindings;
	unsigned int BindingsNeeded;
	unsigned int AbsorbRate;
	unsigned int DrainRate;
	unsigned int HeatExchange;
	unsigned int Structure;
};

RenderStatus currentRenderStatus;


struct Agent
{
	float xDir;
	float yDir;
	Material material;
	int Moistness;
	int Heat;
};

Material Materials[10];
Material NoMaterial;
Agent Agents[WIDTH][HEIGHT];


void CreateMaterials()
{
	NoMaterial.Structure = 0;
	NoMaterial.AbsorbRate = 0;
	NoMaterial.Bindings = 0;
	NoMaterial.BindingsNeeded = 0;
	NoMaterial.DrainRate = 0;
	NoMaterial.HeatExchange = 0;
	NoMaterial.Structure = 0;
	for(unsigned int i = 0; i < 10; i++)
	{
		Material newMaterial;
		newMaterial.Structure = (i*20) +20;
		newMaterial.Bindings = 0;
		newMaterial.BindingsNeeded = rand()%4+1;
		newMaterial.AbsorbRate = (20*i)+20;
		newMaterial.DrainRate = (20*i)+20;
		newMaterial.HeatExchange = (20*i)+20;
		Materials[i] = newMaterial;
	}
}
int currentAgent;

Agent CreateAgent()
{
	Agent newAgent;
	//newAgent.Heat = (rand()%10)*20;
	newAgent.Heat = 0;
	newAgent.Moistness = (rand()%10)*20;
	newAgent.xDir = 0;
	newAgent.yDir = 0;
	newAgent.material = Materials[rand()%10];
	currentAgent++;
	return newAgent;
}

bool SameMaterial(Material& material1, Material& material2);

int CalculateHeatTransferRate(Agent& source, Agent& target)
{
	unsigned int HeatToTransfer = 0;
	int difference = source.Heat - target.Heat;
	if(difference > 0)
	{
		HeatToTransfer = difference;
	}
	
	/*
	if(difference > 0)
	{
		HeatToTransfer = difference;
		if(!SameMaterial(source.material, NoMaterial))
		{
			float delta = 1.0f/256.0f;
			float waterEffect = source.Moistness*delta;
			waterEffect *= 0.5f;
			unsigned int waterheatTransferEffect= unsigned int(float(source.material.HeatExchange)*waterEffect);
			if(HeatToTransfer > unsigned int(float(source.material.HeatExchange*0.5f) +  waterheatTransferEffect))
			{
				HeatToTransfer = unsigned int(float(source.material.HeatExchange*0.5f) +  waterheatTransferEffect);
			}
		}

		else
		{
			float delta = 1.0f/256.0f;
			float waterEffect = source.Moistness*delta;
			HeatToTransfer *= (waterEffect*0.25f);

		}
	}
	*/
	return HeatToTransfer;
}

void HeatExchangePass()
{
	Agent* Top = NULL;
	Agent* Bottom = NULL;
	Agent* Left = NULL;
	Agent* Right = NULL;

	//std::vector<Agent*> randomAccessAgentVector;
	//randomAccessAgentVector.reserve(4);

	AgentVector AgentVec;

	for(int y = 0; y < HEIGHT; y++)
	{
		for(int x = 0; x < WIDTH; x++)
		{
			int relX = randomAccessX[x];
			int relY = randomAccessX[y];


			Agent& source = Agents[relX][relY];

			if((relY-1) >= 0)
			{
				AgentVec.push_back(&Agents[relX][relY-1]);
			}

			if((relY+1) < HEIGHT)
			{
				AgentVec.push_back(&Agents[relX][relY+1]);
			}

			if((relX-1) >= 0)
			{
				AgentVec.push_back(&Agents[relX-1][relY]);
			}

			if((relX+1) < WIDTH)
			{
				AgentVec.push_back(&Agents[relX+1][relY]);
			}

			while(AgentVec.dataelements)
			{
				Agent* target = AgentVec.popRandom();
				int transferRate = int(CalculateHeatTransferRate(source,*target)*0.5f);
				if(transferRate)
				{
					target->Heat+= transferRate;
					Agents[relX][relY].Heat-=transferRate;
					//source.Heat -=1;
				}
			}
			AgentVec.clear();

			if(source.Heat < 0)
			{
				source.Heat = 0;

			}
		}
	}
}

bool SameMaterial(Material& material1, Material& material2)
{
	return (material1.Structure == material2.Structure);
}

int CalculateMoistnessTransferRate(Agent& source,Agent& target)
{
	unsigned int moistToTransfer = 0;

	if(source.Moistness)
	{
		// no transfer if we are frozen.
		if(source.Heat > FREAZEPOINT)
		{
			//Handle a case where there is no actuall material underneath stoping the transfer.
			if(SameMaterial(target.material,NoMaterial))
			{
				moistToTransfer = unsigned int(source.Moistness*0.5f);
				if(moistToTransfer + target.Moistness > 255)
				{
					moistToTransfer = 255-target.Moistness;
				}
			}
			else
			{
				moistToTransfer = unsigned int(source.Moistness*0.15f);
				if(moistToTransfer + target.Moistness > 255)
				{
					moistToTransfer = 255-target.Moistness;
				}
				if(source.material.DrainRate < moistToTransfer)
				{
					moistToTransfer = source.material.DrainRate;
				}
				if(moistToTransfer > target.material.AbsorbRate)
				{
					moistToTransfer = target.material.AbsorbRate;
				}

			}
		}
	}
	return moistToTransfer;
}
void Drain(Agent*source , Agent* target)
{

}

void DrainPass()
{
	AgentVector agentVec;
	agentVec.clear();
	for(int y = 0; y < HEIGHT; y++)
	{
		for(int x = 0; x < WIDTH; x++)
		{
			Agent& Current = Agents[x][y];
			if(y+1 < HEIGHT)
			{
				agentVec.push_back(&Agents[x][y+1]);
			}
			if(y-1 >= 0)
			{
			}
			if(x+1 < WIDTH)
			{
				agentVec.push_back(&Agents[x+1][y]);
			}
			if(x-1 >= 0)
			{
				agentVec.push_back(&Agents[x-1][y]);
			}
			while(agentVec.dataelements)
			{
				Agent* target = agentVec.popRandom();
				int transferRate = int(CalculateMoistnessTransferRate(Current,*target));
				target->Moistness+= transferRate;
				Agents[x][y].Moistness-=transferRate;
			}
			agentVec.clear();
		}
	}
}

void materialReactions()
{
	for(int y = 0; y < HEIGHT; y++)
	{
		for(int x = 0; x < WIDTH; x++)
		{
			Agent& currentAgent = Agents[x][y];
			if(!SameMaterial(currentAgent.material,NoMaterial))
			{
				if(y+1 < HEIGHT)
				{
					Agent& agentBelow = Agents[x][y+1];

					int difference = abs(int (agentBelow.material.Structure) - int(currentAgent.material.Structure));
					if(difference > 19 && difference < 80)
					{
						currentAgent.Heat +=20;
						if(currentAgent.Heat > 255)
						{
							currentAgent.Heat = 255;
						}
					}
					else if(difference > 140)
					{
						currentAgent.Heat -=20;
						if(currentAgent.Heat < 0)
						{
							currentAgent.Heat = 0;
						}
					}
				}
			}
		}
	}
}



void getBinds()
{

}


void AbsorbPass()
{

}


bool EmptyBelow(int x, int y)
{
	Material& currentMaterial = Agents[x][y].material;
	int result = y+1;
	if(result >= HEIGHT )
	{
		return false;
	}
	return ( SameMaterial(Agents[x][y+1].material,NoMaterial));

}

bool EmptyLeft(int x, int y)
{
	Material& currentMaterial = Agents[x][y].material;

	if(x-1 <0)
	{
		return false;
	}
	return ( SameMaterial(Agents[x-1][y].material,NoMaterial));

}

bool EmptyRight(int x, int y)
{
	Material& currentMaterial = Agents[x][y].material;

	if(x+1 > WIDTH)
	{
		return false;
	}
	return ( SameMaterial(Agents[x+1][y].material,NoMaterial));
}


void Swap(int x1, int y1, int x2, int y2)
{
	Agent& firstAgent = Agents[x1][y1];
	Agent& secondAgent = Agents[x2][y2];
	Agent Copy = secondAgent;
	secondAgent = firstAgent;
	firstAgent = Copy;
	firstAgent.Heat -=2;
	secondAgent.Heat -=2;
	if(firstAgent.Heat > 0)
	{
		firstAgent.Heat = 0;
	}
	if(secondAgent.Heat > 0)
	{
		secondAgent.Heat = 0;
	}

}

bool isBound(int x, int y)
{
	int binds = 0;
	Agent& currentAgent = Agents[x][y];

	if( (y-1) >= 0)
	{
		binds +=SameMaterial(currentAgent.material,Agents[x][y-1].material);
	}
	if( (x-1) >= 0)
	{
		binds +=SameMaterial(currentAgent.material,Agents[x-1][y].material);
	}
	if( (y+1) < HEIGHT)
	{
		binds +=SameMaterial(currentAgent.material,Agents[x][y+1].material);
	}
	if( (x+1) < WIDTH)
	{
		binds +=SameMaterial(currentAgent.material,Agents[x+1][y].material);
	}


	if( binds >= currentAgent.material.BindingsNeeded)
	{
		return true;
	}
	return false;
}

void GravityPass()
{
	for(unsigned int y = 0; y < HEIGHT; y++)
	{
		for(unsigned int x = 0; x < HEIGHT; x++)
		{
			int relX = randomAccessX[x];
			int relY = randomAccessY[y];

			if(!isBound(relX,relY))
			{
				const Agent& currentAgent = Agents[relX][relY];
				if(currentAgent.Heat > FREAZEPOINT)
				{
					if(currentAgent.material.Structure)
					{
						if(EmptyBelow(relX,relY))
						{
							Swap(relX,relY,relX,relY+1);
						}
						else
						{
							if(rand()%2 == 0)
							{
								if(EmptyLeft(relX,relY))
								{
									if(EmptyBelow(relX-1,relY))
									{
										Swap(relX,relY,relX-1,relY);
									}
								}
								else if(EmptyRight(relX,relY))
								{
									if(EmptyBelow(relX+1,relY))
									{
										Swap(relX,relY,relX+1,relY);
									}
								}
							}
							else
							{
								if(EmptyRight(relX,relY))
								{
									if(EmptyBelow(relX+1,relY))
									{
										Swap(relX,relY,relX+1,relY);
									}
								}
								else if(EmptyLeft(relX,relY))
								{
									if(EmptyBelow(relX-1,relY))
									{
										Swap(relX,relY,relX-1,relY);
									}
								}
							}
						}
					}
				}
			}

		}
	}

}


////////////////////////////////////////////////////////////
/// Entry point of application
///
/// \return Application exit code
///
////////////////////////////////////////////////////////////
int main()
{
	setupRandomAccessVector();
	srand(666);
	currentAgent = 0;
	currentRenderStatus = NORMAL;
	// Create the main rendering window
	sf::RenderWindow App(sf::VideoMode(WIDTH*2, HEIGHT*2, 32), "SFML Graphics");

	//App.SetFramerateLimit(60);

	// Load the sprite image from a file
	sf::Image Image(WIDTH,HEIGHT);
	//if (!Image.LoadFromFile("opengame.bmp"))
	// return EXIT_FAILURE;

	CreateMaterials();
	for(unsigned int y = 0; y < HEIGHT; y++)
	{
		for(unsigned int x = 0; x < WIDTH; x++)
		{
			Agents[x][y] = CreateAgent();
			if (x > 60 && x < 64)
			{
				Agents[x][y].material = Materials[2];

			}
			if(rand()%5 == 0)
			{
				Agents[x][y].material = NoMaterial;
			}
			if(rand()%30 == 0)
			{
				Agents[x][y].material = Materials[7];
			}


			if(y >= HEIGHT-128 && y < HEIGHT-16)
			{
				Agents[x][y].material = NoMaterial;
			}

		}
	}


	// Create the sprite
	sf::Sprite Sprite(Image);
	Sprite.SetPosition(0.0,0.0);

	// Change its properties
	Sprite.SetScale(2.f, 2.f);

	int showEvery =5;


	// Start game loop
	while (App.IsOpened())
	{

		if(App.GetInput().IsKeyDown(sf::Key::Num1))
		{
			currentRenderStatus = NORMAL;
		}
		else if(App.GetInput().IsKeyDown(sf::Key::Num2))
		{
			currentRenderStatus = HEAT;

		}
		else if(App.GetInput().IsKeyDown(sf::Key::Num3))
		{
			currentRenderStatus = MOIST;
		}
		else if(App.GetInput().IsKeyDown(sf::Key::Num4))
		{
			currentRenderStatus = MATERIAL;
		}

		// Process events
		sf::Event Event;
		while (App.GetEvent(Event))
		{
			// Close window : exit
			if (Event.Type == sf::Event::Closed)
				App.Close();
		}

		HeatExchangePass();
		DrainPass();
		GravityPass();
		materialReactions();
		/*
		*/
		for(unsigned int y = 0; y < HEIGHT; y++)
		{
			for(unsigned int x = 0; x < WIDTH; x++)
			{
				Agent& currentAgent = Agents[x][y];
				//if(currentAgent.Material)
				//{
				if(currentRenderStatus == HEAT)
				{
					Image.SetPixel(x,y,sf::Color(currentAgent.Heat,currentAgent.Heat, currentAgent.Heat));
				}
				else if(currentRenderStatus == MATERIAL)
				{
					Image.SetPixel(x,y,sf::Color(currentAgent.material.Structure,currentAgent.material.Structure, currentAgent.material.Structure));
				}
				else if(currentRenderStatus == MOIST)
				{
					Image.SetPixel(x,y,sf::Color(currentAgent.Moistness,currentAgent.Moistness, currentAgent.Moistness));
				}
				else if(currentRenderStatus == NORMAL)
				{
					int materialcol = currentAgent.material.Structure * 0.5;
					if(!SameMaterial(currentAgent.material, NoMaterial))
					{
						materialcol += 128;
					}
					Image.SetPixel(x,y,sf::Color(materialcol,255, materialcol));
					if(currentAgent.Heat < FREAZEPOINT && currentAgent.Moistness != 0 && !SameMaterial(currentAgent.material, NoMaterial))
					{
						int level = 128;
						level += (currentAgent.Moistness*0.5f);

						Image.SetPixel(x,y,sf::Color(level,level, level));
					}
					else if(SameMaterial(currentAgent.material,NoMaterial))
					{
						int level = currentAgent.Moistness * 0.5;
						if(currentAgent.Moistness)
						{
							level += 128;
						}
						Image.SetPixel(x,y,sf::Color(level,level, 255));
					}
					else if(currentAgent.Heat > FREAZEPOINT)
					{
						Image.SetPixel(x,y,sf::Color(255,currentAgent.Heat, currentAgent.Moistness));
					}
				}
			}
			//Sprite.SetImage(Image);

		}

		//Sleep(20);
		// Display sprite in our window
		//		App.Clear();
		App.Draw(Sprite);

		// Display window contents on screen
		App.Display();
	}

	return EXIT_SUCCESS;
}
